/***********************************************************************************
 * 文件名： TDevCom.h
 * 作者： 刘言
 * 版本： 2
 * 说明：
 * 		与要读写的目标设备的通信协议实现，合并了雾化器设备、电池设备、一体雾化套装设备。
 * 	    通信协议更多细节参考《雾化器通信协议》、《读写器与电池通信协议》、《读写器与一体雾化套
 *  装通信协议》文档。
 *      本文件使用面向对象的思想设计，C++的Class将属性（变量）和函数封装在一起，C语言使用
 *  struct可以一定程度上替代Class。虽然struct也可以利用函数指针变量的方式将函数封装在struct内
 *  部，但是实例化对象时总需要重复的给这些函数指针赋值，会增加一些代码量，而且函数指针本身也会
 *  增加RAM使用量。
 *      本文件面向对象的实现方案是：在struct内只封装属性，通过调用统一的函数来操作不同的对象。
 *  损失了一定的封装性，但是节省了一定的资源，规避了 KEIL C51 编译器可能的函数指针功能不完善引
 *  起的问题。
 *      使用之前需要先实例化（定义）一个TDevCom对象,然后对其进行初始化（设置基础属性）。调用函
 *  数来操作对象时必须将对象地址传递给函数（第一个参数）。
 * 		本文件提供了以下接口函数：
 *      TDevCom_Init: 初始化 TDevCom 对象
 *      TDevCom_GetErrorCode: 获取错误码
 *      TDevCom_SetEncrypt: 设置是否加密通信
 * 		TDevCom_Enter: 进入通信状态，注意一般会影响中断响应
 * 		TDevCom_Exit: 退出通信状态
 * 		TDevCom_Write:  向目标设备的存储器写入数据
 * 		TDevCom_Read: 从目标设备的存储器读出数据
 * 		使用流程: 初始化后，先调用 TDevCom_Enter 与目标设备建立通信同步，然后调用 TDevCom_Write 或者 
 * 		TDevCom_Read 等进行操作，最后 TDevCom_Exit。
 * 修改记录：
 * 	2020/6/8: 初版。 刘言。
 *  2020/6/19: 2 增加 TDevCom_SetEncrypt 。刘言。
***********************************************************************************/
#include "polsys.h"
#include "encrypt.h"
#include "TDevCom.h"

//////////////////////////////// static 变量定义 /////////////////////////////////////

static C51_CODE const cmd_list_t BATT_INTE_CMD_LIST = TDEVCOM_BATT_INTE_CMD_LIST;
static C51_CODE const cmd_list_t ATOM_CMD_LIST = TDEVCOM_ATOM_CMD_LIST;

//////////////////////////////// 私有函数声明 ////////////////////////////////

static void Send(TDevCom *obj, u8 *buff, u8 length);
static void ESend(TDevCom *obj, u8 *buff, u8 length);
static bool Receive(TDevCom *obj, u8 *buff, u8 *plen);
static bool EReceive(TDevCom *obj, u8 *buff, u8 *plen);
static bool TryWrite(TDevCom *obj, u16 addr, const u8 *buff, u8 length);
static bool TryRead(TDevCom *obj, u16 addr, u8 *buff, u8 length);

//////////////////////////////// 接口（public）函数定义 ////////////////////////////////

// 初始化
// obj: 操作的对象
// protocol: 协议类型
// limit_code: 通信限制码
void TDevCom_Init(TDevCom *obj, protocol_t protocol, u8 limit_code)
{
    obj->mProtocol = protocol;
    obj->mLimitCode = limit_code;
    if (protocol == TDEVCOM_ATOMIZER) // 雾化器协议
    {
        obj->pCmdList = &ATOM_CMD_LIST; // 使用雾化器协议使用的命令表
        obj->mEncrypt = true;           // 雾化器通信默认加密
        obj->mWriteTime = false;        // 雾化器通信（WRITE命令）默认不写时间
    }
    else
    {
        obj->pCmdList = &BATT_INTE_CMD_LIST; // 使用电池和一体雾化通信协议使用的命令表
        obj->mEncrypt = false;               // 电池和一体雾化通信默认不加密
        obj->mWriteTime = true;              // 电池和一体雾化通信（WRITE命令）默认写时间
    }
    obj->mBackCode = 0;
    obj->mPassCode = 0;
    obj->mAuthCode = 0;
}

// 获取错误码
IN_LINE TDevCom_error_code_t TDevCom_GetErrorCode(TDevCom *obj)
{
    return obj->mErrorCode;
}

// 设置通信是否加密。目前应该只用于 电池和一体雾化通信 ，因为雾化器通信现在的约定是始终加密，如果把雾
// 化器通信设置为不加密，通信会失败。
// val: true表示加密，false表示不加密
// rand: 加密使用的随机数，应该来自从机设备，TDevCom将用此随机数计算回应码，如果不加密可设置为NULL
void TDevCom_SetEncrypt(TDevCom *obj, bool val, const u8 *rand)
{
    obj->mEncrypt = val;
    obj->mBackCode = Ecpt_GetBackCode(rand, 4); // 计算回应码，如果不加密，此属性无意义。
}

// 同步握手
// obj: 执行的对象
bool TDevCom_Enter(TDevCom *obj)
{
    u8 buff[4];
    u8 len;

    if (obj->mProtocol == TDEVCOM_ATOMIZER) // 雾化器协议
    {
        obj->mErrorCode = (TDevCom_error_code_t)PTC_Start(); // 开始同步 记得Stop！
        if (obj->mErrorCode != PTC_SUCCESS)
            return false;
        if (obj->mEncrypt) // 加密，雾化器协议在此处获取随机数并计算回应码
        {
            if (Receive(obj, buff, &len) == false) // 从机发来的第一包是随机数
                return false;
            obj->mBackCode = Ecpt_GetBackCode(buff, len); // 计算回应码
        }
    }
    else // 电池或者一体雾化协议
    {
        obj->mErrorCode = (TDevCom_error_code_t)TC_Start(); //开始同步 记得Stop！
        if (obj->mErrorCode != TC_SUCCESS)
            return false;
    }

    return true;
}

// 退出通信
// obj: 执行的对象
void TDevCom_Exit(TDevCom *obj)
{
    if (obj->mProtocol == TDEVCOM_ATOMIZER)
        PTC_Stop();
    else
    {
        obj->mEncrypt = false; // 下次再建立通信时默认不加密
        TC_Stop();
    }
}

// 写入，尝试3遍
// obj: 操作的对象
// addr: 要写入的地址
// buff: 要写入的数据
// length：要写入的字节数,<=128
bool TDevCom_Write(TDevCom *obj, u16 addr, const u8 *buff, u8 length)
{
    u8 i;
    bit ret;

    for (i = 0; i < 3; i++)
    {
        ret = TryWrite(obj, addr, buff, length);
        if (ret)
            break;
        else
        {
            if (i < 2)
            {
                TDevCom_Exit(obj);
                if (obj->mProtocol != TDEVCOM_ATOMIZER)
                    Delay_ms(1000); // 等待电池或者一体雾化设备准备好！
                TDevCom_Enter(obj);
            }
        }
    }
    return ret;
}

// 读取，尝试3遍
// obj: 操作的对象
// addr: 读数据起始地址
// buff：读出数据的保存地址
// length：要读的字节数 <=128
bool TDevCom_Read(TDevCom *obj, u16 addr, u8 *buff, u8 length)
{
    u8 i;
    bit ret;

    for (i = 0; i < 3; i++)
    {
        ret = TryRead(obj, addr, buff, length);
        if (ret)
            break;
        else
        {
            if (i < 2)
            {
                TDevCom_Exit(obj);
                if (obj->mProtocol != TDEVCOM_ATOMIZER)
                    Delay_ms(1000); // 等待电池或者一体雾化设备准备好！
                TDevCom_Enter(obj);
            }
        }
    }
    return ret;
}

//////////////////////////////// 私有函数定义 ////////////////////////////////

// 发送
// obj: 执行的对象
// buff: 数据缓存
// length: 长度
static void Send(TDevCom *obj, u8 *buff, u8 length)
{
    if (obj->mProtocol == TDEVCOM_ATOMIZER)
        PTC_Send(buff, length);
    else
        TC_Send(buff, length);
}

// 加密发送,会改变buff中的数据（变成密文）
// obj: 执行的对象
// buff: 数据缓存，最后一个字节会用来存放密码
// length: 长度
static void ESend(TDevCom *obj, u8 *buff, u8 length)
{
    obj->mPassCode = GetRand8bit();                     // 获取随机数作为密码
    buff[length - 1] = obj->mPassCode;                  // 把密码保存到数据中（最后一个字节）
    obj->mAuthCode = Ecpt_GetAuthCode(buff, length);    // 用数据区所有数据（加密前）计算并保存“认证码”
    Ecpt_EncryptData(buff, length - 1, obj->mPassCode); // 使用mPassCode加密数据，不加密密码本身所以长度-1
    Send(obj, buff, length);
}

// 接收
// obj: 执行的对象
// buff: 用来存放数据的缓存
// plen: 用来存放收到的数据长度
static bool Receive(TDevCom *obj, u8 *buff, u8 *plen)
{
    if (obj->mProtocol == TDEVCOM_ATOMIZER)
    {
        obj->mErrorCode = (TDevCom_error_code_t)PTC_Receive(buff, plen); //获取的长度是数据区的长度
        if (obj->mErrorCode != PTC_SUCCESS)
            return false;
    }
    else
    {
        obj->mErrorCode = (TDevCom_error_code_t)TC_Receive(buff, plen); //获取的长度是数据区的长度
        if (obj->mErrorCode != TC_SUCCESS)
            return false;
    }
    return true;
}

// 接收并解密
// obj: 执行的对象
// buff: 用来存放数据的缓存
// plen: 用来存放收到的数据长度
static bool EReceive(TDevCom *obj, u8 *buff, u8 *plen)
{
    idata u8 length;

    // 接收原始数据
    if (Receive(obj, buff, &length) == false)
    {
        return false;
    }
    // 验证认证码是否正确，最后一个数据是认证码
    if (obj->mAuthCode != buff[length - 1])
    {
        obj->mErrorCode = TDEVCOM_AUTH_CODE_ERR;
        return false;
    }
    // 解密数据
    Ecpt_DecryptData(obj->mPassCode, length - 1, buff); //不用解密认证码，所以长度-1
    *plen = length;

    return true;
}

// 尝试一次写入操作（WRITE）
// obj: 操作的对象
// addr: 要写入的地址
// buff: 要写入的数据
// length：要写入的字节数,<=128
static bool TryWrite(TDevCom *obj, u16 addr, const u8 *buff, u8 length)
{
    xdata u8 data_buf[3 + 2 + 8 + 128 + 1]; // 3-命令+地址，2-加密2码，8-时间，1-密码
    idata u8 data_len;
    idata u8 param_index;

    // 准备数据包
    data_len = 0;                                // 数据区长度
    data_buf[data_len++] = obj->pCmdList->WRITE; // 命令：写入
    if (obj->mEncrypt)                           // 需要加密
    {
        data_buf[data_len++] = obj->mBackCode;
        data_buf[data_len++] = obj->mLimitCode;
    }
    if (obj->mWriteTime) // 需要写入时间
    {
        xdata tm_t t;

        Clock_GetTime(&t);
        data_buf[data_len++] = t.Year;   //年
        data_buf[data_len++] = t.Month;  //月
        data_buf[data_len++] = t.Day;    //日
        data_buf[data_len++] = t.Week;   //星期
        data_buf[data_len++] = t.Hour;   //时
        data_buf[data_len++] = t.Minute; //分
        data_buf[data_len++] = t.Second; //秒
        data_buf[data_len++] = t.Zone;   //时区（X10）
    }
    data_buf[data_len++] = HIBYTE(addr);       //高地址
    data_buf[data_len++] = LOBYTE(addr);       //低地址
    memcpy(&data_buf[data_len], buff, length); // 要写入的数据
    data_len += length;
    if (obj->mProtocol == TDEVCOM_ATOMIZER || obj->mEncrypt)
        data_buf[data_len++] = 0; // 密码

    // 发送数据包 并 接收回应包
    if (obj->mEncrypt) // 加密
    {
        param_index = 3; // 参数所在RETURN包的位置
        ESend(obj, data_buf, data_len);
        if (EReceive(obj, data_buf, &data_len) != true)
            return false;
    }
    else // 不加密
    {
        param_index = 1; // 参数所在RETURN包的位置
        Send(obj, data_buf, data_len);
        if (Receive(obj, data_buf, &data_len) != true)
            return false;
    }

    // 处理回应
    if (data_buf[0] == obj->pCmdList->RETURN) //正确的回应
    {
        obj->mErrorCode = data_buf[param_index]; // 获取操作结果
        if (obj->mErrorCode != 0)
            return false; // 从机返回了错误
        if (obj->mProtocol == TDEVCOM_ATOMIZER)
        {
            PTC_TemporaryPower(data_buf[param_index + 2]); //强输出给雾化器提供电能保存数据
        }
    }
    else // 返回的命令无法识别
    {
        obj->mErrorCode = TDEVCOM_UNKNOW_CMD;
        return false;
    }
    return true;
}

// 尝试一次读取操作（READ）
// obj: 操作的对象
// addr: 读数据起始地址
// buff：读出数据的保存地址
// length：要读的字节数 <=128
static bool TryRead(TDevCom *obj, u16 addr, u8 *buff, u8 length)
{
    xdata u8 data_buf[1 + 2 + 128 + 1]; // 1-命令，2-加密2码，1-认证码
    u8 data_len;
    u8 param_index;

    //if(length == 0)return PTC_PARA_ERROR;

    // 准备数据包
    data_len = 0;                               // 数据区长度
    data_buf[data_len++] = obj->pCmdList->READ; // 命令：读取
    if (obj->mEncrypt)                          // 需要加密
    {
        data_buf[data_len++] = obj->mBackCode;  // 回应码
        data_buf[data_len++] = obj->mLimitCode; // 通信限制码
    }

    data_buf[data_len++] = HIBYTE(addr); // 地址H
    data_buf[data_len++] = LOBYTE(addr); // 地址L
    if (obj->mProtocol == TDEVCOM_BATTERY_INTEGRATED)
        data_buf[data_len++] = 0;  // 电池和一体雾化通信协议中数据长度的高字节
    data_buf[data_len++] = length; // 要读出的数据长度（字节数）
    data_buf[data_len++] = 0;      // 密码（电池和一体雾化协议的明文READ需要0密码，与雾化器协议一样）

    // 发送数据包 并 接收回应包
    if (obj->mEncrypt)
    {
        param_index = 3; // 参数所在回应数据包的索引
        ESend(obj, data_buf, data_len);
        if (EReceive(obj, data_buf, &data_len) != true)
            return false;
    }
    else
    {
        param_index = 1; // 参数所在回应数据包的索引
        Send(obj, data_buf, data_len);
        if (Receive(obj, data_buf, &data_len) != true)
            return false;
    }

    // 处理回应
    if (data_buf[0] == obj->pCmdList->DATA) // 返回了数据
    {
        memcpy(buff, data_buf + param_index, length);
        if (obj->mProtocol == TDEVCOM_ATOMIZER)
        {
            Delay_ms(3); // 雾化器需要时间准备接收下一包数据 ！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！
        }
    }
    else if (data_buf[0] == obj->pCmdList->RETURN) // 返回了错误
    {
        obj->mErrorCode = data_buf[param_index];
        return false;
    }
    else // 返回的命令无法识别
    {
        obj->mErrorCode = TDEVCOM_UNKNOW_CMD;
        return false;
    }

    return true;
}
